/************************************************************************
* Programmer:  Philippe Gambling
*
* Course:  CSCI 4534
*
* Date:  April 6, 2009
*
* Assignment:  #3
*
* Environment:  GCC 3.2.2 running on diamond.rocks.uhcl.edu
*
* Files Included: server.c, pcb.h, queue.h, server
*
* Purpose:  Receives a PCB containing an array of CPU and I/O burst times 
*           and an arrival time from multiple client programs, 
*           simulates a first come first server scheduler to calculate completion,
*           waiting, turnaround, and blocked times for each PCB received,
*           and returns each updated PCB to the client program that sent it.
*
* Input:  The number of expected clients entered by the user,
*         multiple PCB structs from each client program.
*
* Preconditions:  The server program is started before the clients.
*
* Output:  The CPU utilization, average turnaround, waiting, 
*          and blocked times for all processes.
*
* Postconditions:  The server terminates after sending the result to each client.
*                  FIFO_TO_SERVER is deleted.
*
* Algorithm:
*          Prompt user for the number of clients, 
*           exit if less than 1 or greater than MAX_NUM_CLIENTS
*          Create FIFO_TO_SERVER
*          Open FIFO_TO_SERVER as read only
*          Loop until data read from the expected number of clients
*            Read a PCB struct from FIFO_TO_SERVER to the PCB array
*          Sort the PCB array in order of arrival times          
*          Execute CPU scheduler with PCB array as a parameter
*          (Note: Using round robin algorithm from program2
*                 with a very high time quantum value)
*            set clock to 0
*            loop while ReadyQueue not empty
*                       or BlockedQueue not empty 
*                       or RunState not clear
*                       or "unarrived" processes are still in PCB array
*              check PCB array for arriving processes
*                enqueue each process in ready state queue
*              if time slice expired and a process is in run state
*                enqueue process in run state
*                clear run state
*              if run state free and ReadyQueue is not empty
*                get next PCB from ReadyQueue
*                assign to run state
*                reset time slice to quantum value
*              increment the system clock
*              iterate ReadyQueue and increment each PCB's WaitTime
*              For first node in BlockedQueue
*                decrement current IO burst value
*                increment the BlockedIO value
*                if burst value equals 0
*                  remove from BlockedQueue
*                  enqueue in ReadyQueue
*              iterate BlockedQueue after first node 
*                increment BlockedWaiting time
*              if run state free
*                increment total idle time
*              else
*                decrement current PCB's CPU Burst
*                decrement remaining time slice value
*                if current PCB's cpu burst equals 0
*                  if next element in current PCB's BurstValues is not 0
*                    enqueue PCB in BlockedQueue
*                  else 
*                    set CompletionTime equal to clock
*                    set PCB's TurnaroundTime to CompletionTime - ArrivalTime
*                    add complete process's wait time to total wait time
*                    add complete process's turnaround time to total turnaround time
*                    add complete process's BlockedIO time to total BlockedIO time
*                    add complete process's BlockedWaiting time to total BlockedWaiting time
*                  clear run state variable
*            Calculate average of all TurnaroundTime values in PCB array
*            Calculate average of all WaitTime values in PCB array
*            Calculate average of all BlockedIO values in PCB array
*            Calculate average of all BlockedWaiting values in PCB array
*            Calculate CPU utilization as 100*(clock - total idle time)/clock
*          Exit CPU scheduler
*          For each element in PCB array
*            Open ClientFifoName as write only (ClientFifoName is the private fifo 
*            name received from client)
*            Write the PCB to ClientFifoName
*            Close ClientFifoName
*          Print CPU utilization, average turnaround time, and average wait time
*          Close FIF0_TO_SERVER
*          Unlink FIFO_TO_SERVER
***********************************************************************/ 

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include <fcntl.h>
#include <limits.h>

/* Custom includes created for this assignment */
#include "queue/queue.h"
#include "pcb.h"  // Defines a struct to model a process control block

#define true 1
#define false 0
#define MAX_NUM_CLIENTS 25 // Maximum number of clients supported by server

/* Function prototypes */
void SortByArrival(PCB array[],int length);
void RunCpuScheduler(PCB processes[],float * cpuUtil, float * avgWait, float * avgTurn, float * avgBlockIO, float * avgBlockWait);

extern int errno;

int main(int argc, char * argv[])
{
  int fda;		// to read from client
  int fdb;		// to write to client
  int i;      // Loop counter   
  int numClients = 0; // Number of clients
  float averageWait = 0; // Average process wait time
  float averageTurnaround = 0; // Average process turnaround time
  float averageBlockIO = 0; // Average time process is blocked doing IO
  float averageBlockWait = 0; // Average time process is blocked waiting for IO
  float cpuUtilization = 0; // CPU utilization
  char testOutputFile[25]; // Name of file to write test results to
  int testmode = 0; // Output extra information for testing
  
  PCB processes[MAX_NUM_CLIENTS+1]; // Holds all processes received from clients (the extra PCB marks the end of the array)
  for(i=1;i<=MAX_NUM_CLIENTS;i++) processes[i] = NEW_PCB; // Initialize the proccesses array
  
  // Get expected number of clients
  if(argc < 2) {
    printf("Usage: ./server NUM_CLIENTS\n");
    printf("      NUM_CLIENTS - The number of expected clients (positive whole number)\n");    
    exit(-1);
  }
  else {
    numClients = atoi(argv[1]);
    if(argc > 2) {
      strcpy(testOutputFile,argv[2]);
      testmode = 1;
    }
  }
  // Make sure number of clients is at least 1 and did not exceed MAX_NUM_CLIENTS
  if(numClients<0) 
  {
    perror("Number of clients must be at least 1, exiting...");
	  exit(-2);
  }
  else
  if(numClients>MAX_NUM_CLIENTS)
  {
    perror("Number of clients can not exceed maximum number of clients, exiting...");
	  exit(-3);
  }

  // Create the FIFO_TO_SERVER
  if ((mkfifo("FIFO_TO_SERVER",0666)<0 && errno != EEXIST))
  {
	  perror("cant create FIFO_TO_SERVER");
	  exit(-1);
  }

  // Open FIFO_TO_SERVER to receive data from clients
  if((fda=open("FIFO_TO_SERVER", O_RDONLY))<0)
	  printf("cant open FIFO_TO_SERVER to read");

  // Read each clients' PCB
  i = 0;

  while(i<numClients)
  {
    // Read data from client
    if(read(fda, &processes[i], sizeof(PCB))>0) 
    { 
      printf("Server: Received the following message from client: \n");
      PCB_Print(&processes[i]); 
      i++;  // Increment loop counter if more than 0 bytes read
    }
  }
  
  // Sort processes by arrival time
  SortByArrival(processes, numClients);

  // Run processes through CPU scheduler  
  RunCpuScheduler(processes,
                  &cpuUtilization,
                  &averageWait,
                  &averageTurnaround, 
                  &averageBlockIO, 
                  &averageBlockWait);

  for(i=0;i<numClients;i++)
  {
    // Open ClientFifoName to send data to clients
    if((fdb=open(processes[i].ClientFifoName, O_WRONLY))<0) {
      printf("cant open %s to write", processes[i].ClientFifoName); }
    
    // Send the result back to each client
    write(fdb, &processes[i], sizeof(PCB));
    printf("Server: Sent the results to client\n");
    
    close(fdb);
  }
  printf("Server: CPU utilization = %.2f%%\n",cpuUtilization);
  printf("Server: Average turnaround time = %.2f\n",averageTurnaround);
  printf("Server: Average wait time = %.2f\n",averageWait);
  printf("Server: Average blocked time doing I/O = %.2f\n",averageBlockIO);  
  printf("Server: Average blocked time waiting for I/O = %.2f\n",averageBlockWait);  
  printf("Server: all done, Disconnecting.\n ");

  close(fda);
  unlink("FIFO_TO_SERVER");
  
  if(testmode) { // Print important data to a file
    FILE *file;
    file = fopen(testOutputFile,"w");
    fprintf(file,"Number of clients = %d\n",numClients);
    fprintf(file,"CPU utilization = %.2f%%\n",cpuUtilization);
    fprintf(file,"Average turnaround time = %.2f\n",averageTurnaround);
    fprintf(file,"Average wait time = %.2f\n",averageWait);
    fprintf(file,"Average blocked in I/O time = %.2f\n",averageBlockIO);    
    fprintf(file,"Average blocked waiting time = %.2f\n",averageBlockWait);        
    fclose(file);
  }
  
  return 0;
}

#define swap(x,y) temp = x; \
                x = y; \
                y = temp;
void SortByArrival(PCB array[],int length)
/************************************************************************
* Purpose:  To sort the processes array by arrival times
*
* Parameters:  PCB array and the number of elements in the array
*
* Action:  Array is sorted by arrival times.
*
* Algorithm: Uses cocktail sort.
***********************************************************************/ 
{
  int swapped = false;
  int i = 0;
  PCB temp = NEW_PCB;
  
  do {
    swapped = false;
    for (i=0; i<=(length-2); i++) {
      if (array[i].ArrivalTime > array[i+1].ArrivalTime) { // test whether the two elements are in the wrong order
        swap(array[i],array[i+1]); // let the two elements change places
        swapped = true;
      }
    }
    if (swapped == false) {
      // we can exit the outer loop here if no swaps occurred.
      break;
    }
    swapped = false;
    for (i=length-2; i>=0; i--) {
      if (array[i].ArrivalTime > array[i+1].ArrivalTime) {
        swap(array[i],array[i+1]);
        swapped = true;
      }
    }
  } while (swapped); // if no elements have been swapped, then the list is sorted
}
#undef swap

void RunCpuScheduler(PCB processes[],float * cpuUtil, float * avgWait, float * avgTurn, float * avgBlockIO, float * avgBlockWait) {
/************************************************************************
* Purpose:  To simulate a CPU scheduler.  
*
* Parameters: An array PCB structs, represents all processes.
*             float * cpuUtil, returns CPU utilization.
*             float * avgWait, returns average wait time.
*             float * avgTurn, return average turnaround time.
*             float * avgBlockIO, return average time blocked doing IO.
*             float * avgBlockWait, return average time blocked waiting for IO.
*
* Action:  All PCBs pointed to by ReadyQueue are updated and the queue is empty.
*
* Algorithm: First come first server scheduler (Implemented using Round robin scheduler with maximum int value)
***********************************************************************/ 
  Queue ReadyQueue = new_Queue(sizeof(PCB *)); // Models ready state as a queue of PCB pointers
  Queue BlockedQueue = new_Queue(sizeof(PCB *)); // Models blocked state as a queue of PCB pointers
  PCB * RunState = NULL; // Models running state
  PCB * temp = NULL;     // Used to move PCBs between states
  PCB * nextProcess = &processes[0]; // Next arriving process
  int quantum = INT_MAX; // RR time quantum set to max int value to enable first come first serve behavior  
  int idleTime = 0;      // Total time run state is not in use
  int numProcesses = 0;  // Total processes arrived to scheduler
  int clock = 0; // Start system clock at 0
  int timeSlice = 0; // Remaining time that process can be in RunState
  
  // Clear CPU scheduler metrics
  *cpuUtil = 0;
  *avgWait = 0;
  *avgTurn = 0;
  *avgBlockIO = 0;
  *avgBlockWait = 0;      

  while(!Queue_Empty(ReadyQueue) ||   // Run until all waiting processes,
        !Queue_Empty(BlockedQueue) || // blocked processes,
        RunState != NULL ||           // process currently running, and
        nextProcess->Pid != 0)        // and unarrived proccesses have completed.
  {
    // Check if any processes have arrived
    while((nextProcess->ArrivalTime == clock) && (nextProcess->Pid != 0)) {
      printf("Server: Process %d arrived at %d\n",nextProcess->Pid, clock);    
      Queue_AddBack(ReadyQueue,&nextProcess);
      numProcesses++;
      nextProcess = &processes[numProcesses];
    }
    // if time quantum reached and a process is in RunState
    if(!(timeSlice) && RunState) { 
      printf("Server: Time slice complete at %d\n", clock);
      Queue_AddBack(ReadyQueue,&RunState); // Move running process back to ready state
      RunState = NULL; // Clear run state
    }
    // if run state is free get next process from ReadyQueue and assign to run state
    if(RunState == NULL && !Queue_Empty(ReadyQueue)) { // if run state is free
      // get next process from ReadyQueue and assign to run state
      Queue_RemoveFront(ReadyQueue,&RunState); 
      timeSlice = quantum;
      printf("Server: Process %d moved to RunState at %d\n",RunState->Pid, clock);
    }
    // Increment the clock
    clock++;
    // Iterate ReadyQueue and increment each waiting process's WaitTime
    Queue_StartIterator(ReadyQueue);
    while(Queue_NextNode(ReadyQueue,&temp) != EEND) {
      printf("Server: Incremented process %d wait time at %d\n",temp->Pid,clock);
      temp->WaitTime++;
    } 
    // Iterate BlockedQueue to update IO burst and blocked time values
    Queue_StartIterator(BlockedQueue);
    if(Queue_NextNode(BlockedQueue,&temp) != EEND) {
      printf("Server: Process %d doing I/O at %d\n",temp->Pid,clock);
      temp->BlockedIO++;
      if(--(temp->BurstValues[temp->BurstIndex])==0) { // Decrement IO burst
        // move PCB to ready state if IO is done
        temp->BurstIndex++; // Increment to next CPU burst
        Queue_RemoveFront(BlockedQueue,&temp);
        Queue_AddBack(ReadyQueue,&temp);
      }
    }
    while(Queue_NextNode(BlockedQueue,&temp) != EEND) {
      printf("Server: Incremented process %d blocked wait time at %d\n",temp->Pid,clock);
      temp->BlockedWait++;
    }     
    if(RunState == NULL) { // count cycle as idle if no process ready 
      idleTime++;
      printf("Server: Idle cycle at %d\n", clock);      
    }
    else { // cycle time spent on a process
      timeSlice--; // decrement remaining time slice
      if(--(RunState->BurstValues[RunState->BurstIndex]) == 0) { // CPU burst complete
        // Increment to the next burst value if not at end of array
        RunState->BurstIndex += (RunState->BurstIndex < (MAX_BURSTS-1));
        if(RunState->BurstValues[RunState->BurstIndex]!=0) { // I/O burst next
          Queue_AddBack(BlockedQueue,&RunState);
        }
        else { // Process complete
          printf("Server: Process %d completed at %d\n",RunState->Pid, clock);
          RunState->CompletionTime = clock;
          RunState->TurnaroundTime = RunState->CompletionTime - RunState->ArrivalTime;
          *avgTurn += RunState->TurnaroundTime;
          *avgWait += RunState->WaitTime;
          *avgBlockIO += RunState->BlockedIO;
          *avgBlockWait += RunState->BlockedWait;          
        }
        RunState = NULL;  // Process terminates
      }   
    }

  } // End main while

  // Calculate CPU scheduler metrics
  *avgTurn/=numProcesses;
  *avgWait/=numProcesses;
  *avgBlockWait/=numProcesses;
  *avgBlockIO/=numProcesses;  
  *cpuUtil = (clock-idleTime)*100.0/clock;
  
  // Clean up
  free_Queue(ReadyQueue);
  free_Queue(BlockedQueue);  
}
